<?php

/** 
====================================================================================================
   	XSL BUILDER
    Copyright (C) 2011 John Bakker <me@johnbakker.name>

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
===================================================================================================
 */
/**
 * @name XSL builder
 * @author John Bakker <me@johnbakker.name>
 * @version 0.2
 * PLEASE READ THE README for example code
 */

class xslbuilder
{
	const XSLNS="http://www.w3.org/1999/XSL/Transform";
	public static $vars=array();
	public static $files=array();
	public static $debug=false;
	public static $echoOutput=false;
	/**
	 * Reset all XSL builder info.
	 */
	public static function reset()
	{
		self::$vars=array();
		self::$files=array();
		self::$debug=false;
		self::$echoOutput=false;
	}
	/**
 	 * Builds an XSL file. Used to build a set of XSL files into single XSL saving file access time when reading XSL files.
	 * if outputfile is empty, the 
	 * @param string $inputFile absolute path of the source files for the XSL
	 * @param string $outputFile the absolute path of the target files. 
	 * @param array $modes the modes to build (read the README!!)
     * @return void
     * @access public
     * @static
     */
 	public static function buildXSLFile($inputFile,$outputFile='',$modes=array(''))
	{
		self::$vars['input']=$inputFile;
		self::$vars['output']=$outputFile;
		self::$vars['modes']=implode(",",$modes);
		return self::buildXSL();	
	}
	/**
 	 * Builds an XSL file. 
	 * PLEASE NOTE THIS IS FOR CLI ONLY!
	 * @param array $vars check out the README for information on how to use it !
     * @return void
     * @access public
     * @static
     */
	public static function buildXSLCLI($vars)
	{
		self::$debug=true;
		self::$vars = $vars;
		
		$return =  self::buildXSL();
		
		if(is_array($return))
		{
			//its an array 
			foreach($return as $mode=>$xsl)
			{
				echo "\n============\nMODE:".$mode."\n============\n\n";
			}
		}
		else {
			echo $return;
		}
	}
	/** 
	 * private method that actually does the building
	 */
	private static function buildXSL()
	{
		
		$vars = self::$vars;
		$buildmodes=explode(",",$vars['modes']);
		
		self::loadDocument($vars['input']);
		self::$files[$vars['input']]->findAndLoadImports();
		if(count($buildmodes)==0)
		{
			$buildmodes=array('');//make sure we at least build the default one .
		}
		
		if($vars['output']=='')
		{
			if(count($buildmodes)>1)
			{
				//build it and return an array
				$xsls = array();
				foreach($buildmodes as $mode)
				{
					$xsls[$mode]= self::saveXML($mode);
				}
				return $xsls;
			}
			else 
			{
				$xml = self::saveXML($mode);
				return $xml;
			}
			
			
		}
		else 
		{
			
		
			$path_parts = pathinfo($vars['output']);
			foreach($buildmodes as $mode)
			{
				
				$filename=$path_parts['dirname'].'/'.$path_parts['filename'].'.built.'.($mode!=''?$mode.'.':'').$path_parts['extension'];
				$xml = self::saveXML($mode);
				
				self::debug('writing to '.$filename,'filesize:'.strlen($xml));
				file_put_contents($filename, $xml);
			}
		}
		
	}
	//After all the loading, this is called to actually get all components and merge them into the 
	private static function saveXML($mode)
	{
		$sd = new DOMDocument('1.0','UTF-8');
		$sd->loadXML(file_get_contents(self::$vars['input']));
		
		$nodesToDo = array('output','preserve-space','processing-instruction','param','variable','template','import');
		$fc = $sd->firstChild;
		foreach($nodesToDo as $nn)
		{
			$nodes = $sd->getElementsByTagNameNS(self::XSLNS,$nn);
			
			for($o=$nodes->length-1;$o>=0;$o--)
			{
				//now remove the imports
				if($nodes->item($o)->parentNode->nodeName!='xsl:stylesheet')
				{
					continue;
				}
				$fc->removeChild($nodes->item($o));
			}		

		}
		
		//clean up some whitespace
		
		$lines= explode("\n",$sd->saveXML());
		$olines = array();
		for($o=0;$o<count($lines);$o++)
		{
			if($lines[$o]=="")
			{
				continue;
			}
			$olines[]=$lines[$o];
		}
		$xml = implode("\n",$olines);
		$sd = new DOMDocument('1.0','UTF-8');
		$sd->loadXML($xml);
		$fc = $sd->firstChild;
		
		foreach($nodesToDo as $nn)
		{
			if($nn=='import')
			{
				//ofcource ignore the imports...
				continue;
			}
			$nodeName = 'xsl:'.$nn;
			foreach(self::$files as $filename=>$doc)
			{
				
				if($mode!='')
				{
	
					//perhaps  continue
					if($doc->modes!='')
					{
						$modeparts = explode(",",$doc->modes);
						if(!in_array($mode,$modeparts))
						{
							//skip it 
							continue;
						}
					}
				}
				//import the nodes
				foreach($doc->nodes as $n)
				{

					if($n->nodeName!=$nodeName)
					{
						continue;
					}
					$fc->appendChild($sd->importNode($n,true));
				}			
	
			}
		}
		$xml = $sd->saveXML();
		unset($sd);
		return $xml;
	}
	//helper function for debugging :)
	public static function debug($subject,$text)
	{
		if(self::$debug)
		{
			echo "\n======\n".$subject."\n".$text."\n";
		}
	}
	//Loads the document... but only if it hasn't been loaded before :)
	public static function loadDocument($filename,$modes='')
	{
		if(!isset(self::$files[$filename]))
		{
			self::debug("attempting to load",$filename);	
			self::$files[$filename] = new xslBuilderDocument($filename,$modes);
			return true;
		}
		else 
		{
			self::debug("attempting to load failed, already loaded",$filename);	
			return false;
		}
	}
}
/**
 * @name xslBuilderDocument
 * @author a helper class to actually load the XSL and extract the needed nodes :)
 */
class xslBuilderDocument
{
	
	public $pathinfo = false;
	public $dom= false;
	public  $modes=false;
	public $nodes=array();
	
	const XSLNS="http://www.w3.org/1999/XSL/Transform";
	public function __construct($filename,$modes)
	{
		$this->pathInfo = pathinfo($filename);
		$this->modes=$modes;
		$this->dom = new DOMDocument("1.0","UTF-8");
		$this->dom->preserveWhiteSpace=true;
		
		$this->dom->load($filename);
		
	}
	//extracts the xsl and its nodes... and all imports in there.
	public function findAndLoadImports()
	{
		$nodesToDo = array('output','preserve-space','processing-instruction','param','variable','template','import');
		
		foreach($nodesToDo as $nn)
		{
			$nodes = $this->dom->getElementsByTagNameNS(self::XSLNS,$nn);
			for($o=0;$o<$nodes->length;$o++)
			{
				$node = $nodes->item($o);
				
				if($node->parentNode->nodeName=='xsl:stylesheet')
				{
					$this->nodes[]=$node;
					
				}
				
			}
		}
		$imports = $this->dom->getElementsByTagNameNS(self::XSLNS,'import');
		for($o=0;$o<$imports->length;$o++)
		{
			$import = $imports->item($o);
			$modes = $import->getAttribute("xslbuildermodes");
			$href = $import->getAttribute("href");
			self::debug('found import for modes:'.$modes,$href);
			//okay so we found it we need it then....
						
			if(substr($href,0,1)!='/')
			{
				//relative path
				$href = $this->figureOutRelativePath($href);
			}
			if(xslbuilder::loadDocument($href,$modes))
			{
				xslbuilder::$files[$href]->findAndLoadImports();	
			}
			
			
			
		}


	}
	/**
	 * Figures out the relative path for an XSL
	 */
	public function figureOutRelativePath($path)
	{
		$origpath = getcwd();
		self::debug("dirname",$this->pathInfo['dirname']);
		
		chdir($this->pathInfo['dirname']);
		$rp = realpath($path);
		chdir($origpath);
		return $rp;
	}
	// a helper method for debugging
	public static function debug($subject,$text)
	{
		if(xslbuilder::$debug)
		{
			echo "\n======\n".$subject."\n".$text."\n";
		}
	}	
}
